package etcd_sdr

import (
	"context"
	"errors"
	"etcd-sdr/pkg/maputils"
	jsoniter "github.com/json-iterator/go"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/resolver"
	"strings"
	"time"
)

const (
	scheme = "sdr"
)

type SdrBuilderOption func(*SdrBuilder) error

type SdrBuilder struct {
	// scheme        string
	namespace     *string
	ctx           context.Context
	cancelFunc    context.CancelFunc
	client        *clientv3.Client
	log           *zap.Logger
	refreshTicker *time.Ticker
	ignores       *maputils.Map[string, struct{}]
}

func NewSdrServerURL(addr, serverName string) string {
	return strings.Join([]string{scheme, "://", addr, "/", serverName}, "")
}

func NewGrpcDialTarget(serverName string) string {
	return strings.Join([]string{scheme, ":///", serverName}, "")
}

func NewGrpcDialWithDefaultOpts(serverName string) (*grpc.ClientConn, error) {
	return grpc.Dial(strings.Join([]string{scheme, ":///", serverName}, ""), grpc.WithInsecure(), grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`))
}

func NewSdrBuilderWithClient(
	ctx context.Context,
	cancelFunc context.CancelFunc,
	namespace *string,
	client *clientv3.Client,
	refreshTime time.Duration,
	log *zap.Logger,
	opts ...SdrBuilderOption,
) (*SdrBuilder, error) {
	builder := SdrBuilder{
		namespace:     namespace,
		ctx:           ctx,
		cancelFunc:    cancelFunc,
		client:        client,
		log:           log,
		refreshTicker: time.NewTicker(refreshTime),
		ignores:       maputils.New[string, struct{}](),
	}

	for i := range opts {
		if err := opts[i](&builder); err != nil {
			return nil, err
		}
	}

	return &builder, nil
}

func (s *SdrBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	_resolver, err := newEtcdResolver(s.namespace, s.ctx, s.client, s.refreshTicker, target, cc, opts, s.log, s.ignores)
	if err != nil {
		return nil, err
	}

	go _resolver.watch()

	return _resolver, nil
}

func (s *SdrBuilder) Scheme() string {
	return scheme
}

// WithIgnoresOption For distributed deployment, manually add services that need to be ignored.
func WithIgnoresOption(infos ...*ServerInfo) SdrBuilderOption {
	return func(builder *SdrBuilder) error {
		ignoreMap := maputils.New[string, struct{}]()
		for i := range infos {
			ignoreMap.Store(infos[i].GetServerInfoKey(), struct{}{})
		}
		builder.ignores = ignoreMap
		return nil
	}
}

func (s *SdrBuilder) GetAllHostFormNaming(ctx context.Context, naming string) ([]string, error) {
	prefixKey := naming
	if s.namespace != nil {
		prefixKey = strings.TrimSpace(strings.Join([]string{*s.namespace, naming}, "::"))
	}

	getResp, err := s.client.Get(ctx, prefixKey, clientv3.WithPrefix())
	if err != nil {
		return nil, err
	}

	respLen := len(getResp.Kvs)

	if respLen < 1 {
		return nil, errors.New("sdr: not has naming server err")
	}

	address := make([]string, respLen)
	for i := range getResp.Kvs {
		var info *ServerInfo
		_ = jsoniter.Unmarshal(getResp.Kvs[i].Value, &info)
		address[i] = info.GetHostAddress()
	}
	return address, nil
}

func (s *SdrBuilder) GetAllHostFormNamingWithIgnore(ctx context.Context, naming string) ([]string, error) {
	prefixKey := naming
	if s.namespace != nil {
		prefixKey = strings.TrimSpace(strings.Join([]string{*s.namespace, naming}, "::"))
	}

	getResp, err := s.client.Get(ctx, prefixKey, clientv3.WithPrefix())
	if err != nil {
		return nil, err
	}

	respLen := len(getResp.Kvs)

	if respLen < 1 {
		return nil, errors.New("sdr: not has naming server err")
	}

	//address := make([]string, respLen)
	var addresses []string
	for i := range getResp.Kvs {
		var info *ServerInfo
		_ = jsoniter.Unmarshal(getResp.Kvs[i].Value, &info)

		// ignore
		if _, ok := s.ignores.Load(info.GetServerInfoKey()); ok {
			continue
		}

		addresses = append(addresses, info.GetHostAddress())
	}
	return addresses, nil
}
